﻿using System;
using System.Data;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using Tyche.Common.ProxyBuilder;
using Tyche.Common.Resource;
using Tyche.DAO.Mapper;
using Tyche.DAO.Operations;
using Tyche.DAO.Transaction;

namespace Tyche.DAO
{
    public class TycheDAOBuilder : ITycheProxyBuilder
    {
        /// <summary>
        /// 构建代理
        /// </summary>
        /// <param name="moduleBuilder"></param>
        /// <param name="type"></param>
        public void Build(ModuleBuilder moduleBuilder, Type type)
        {
            var mapperResource = TycheResourceManager.GetResource<ITycheMapper>();

            var resource = TycheResourceManager.GetResource(type);

            if (resource.Proxy != null)
            {
                return;
            }

            var typeBuilder = moduleBuilder.DefineType(resource.Interface.Name.TrimStart('I')
                , TypeAttributes.Class | TypeAttributes.Public
                , null
                , new Type[] { resource.Interface });

            // 开始构建代理类的方法
            // 要求方法必须具有返回值(如无返回值是没有意义的)且最多只有一个非基础类型的参数
            // 思路：
            // 1、代理方法调用原实现类中对应的方法
            // 2、执行原实现类方法前后会调用各个Filter
            // 3、利用EMIT构建代理方法体

            foreach (var method in type.GetMethods().Where(m => m.ReturnType != typeof(void)))
            {
                // 参数类型
                var parameterTypes = method.GetParameters().Select(p => { return p.ParameterType; }).ToArray();

                if (parameterTypes.Length > 1 || (parameterTypes.Length == 1 && parameterTypes[0].IsPrimitive))
                {
                    throw new Exception("TycheDAO中的方法最多只能有最多一个参数且该参数必须不能为基础类型");
                }

                var methodBuilder = typeBuilder.DefineMethod(method.Name
                        , MethodAttributes.Public
                        | MethodAttributes.HideBySig
                        | MethodAttributes.NewSlot
                        | MethodAttributes.Virtual
                        | MethodAttributes.Final
                        , method.ReturnType
                        , parameterTypes);

                var generator = methodBuilder.GetILGenerator();

                var mapper = generator.DeclareLocal(typeof(ITycheMapper));
                var transacion = generator.DeclareLocal(typeof(IDbTransaction));
                var result = generator.DeclareLocal(method.ReturnType);
                var normal = generator.DefineLabel();

                // 要传入的Sql
                var sql = generator.DeclareLocal(typeof(string));

                generator.Emit(OpCodes.Ldstr, method.Name);

                if (parameterTypes.Length == 1)
                {
                    generator.Emit(OpCodes.Ldarg_1);
                    generator.Emit(OpCodes.Call, typeof(TycheSQLCompiler).GetMethod("Compile", BindingFlags.Public | BindingFlags.Static));
                }
                else
                {
                    generator.Emit(OpCodes.Ldnull);
                    generator.Emit(OpCodes.Call, typeof(TycheSQLCompiler).GetMethod("Compile", BindingFlags.Public | BindingFlags.Static));
                }

                generator.Emit(OpCodes.Stloc_S, sql);

                // 要传入的参数
                var parameter = generator.DeclareLocal(parameterTypes[0]);

                generator.Emit(OpCodes.Ldarg_1);
                generator.Emit(OpCodes.Stloc_S, parameter);

                // 从TycheTransactionManager获取是否需要开启事务的状态
                // 如果需要且操作不为Select则从TycheTransactionManager获取mapper和dbTransaction
                // 如果是Select则忽略事务(创建新的mapper)

                if (method.GetCustomAttributes(typeof(TycheSelectAttribute)).Any())
                {
                    generator.Emit(OpCodes.Br_S, normal);
                }

                generator.Emit(OpCodes.Call, typeof(TycheTransactionManager).GetMethod("IsNeddTransaction", BindingFlags.Public | BindingFlags.Static));
                generator.Emit(OpCodes.Brfalse_S, normal);

                generator.Emit(OpCodes.Call, typeof(TycheTransactionManager).GetMethod("GetMapper", BindingFlags.Public | BindingFlags.Static));
                generator.Emit(OpCodes.Ldloc_S, sql);
                generator.Emit(OpCodes.Ldloc_S, parameter);
                generator.Emit(OpCodes.Call, typeof(TycheTransactionManager).GetMethod("GetDbTransaction", BindingFlags.Public | BindingFlags.Static));

                if (method.GetCustomAttributes(typeof(TycheInsertAttribute)).Any())
                {
                    if (((TycheInsertAttribute)method.GetCustomAttributes(typeof(TycheInsertAttribute)).First()).ReturnId)
                    {
                        generator.Emit(OpCodes.Callvirt, typeof(ITycheMapper)
                            .GetMethod("InsertAndReturnId", new Type[] { typeof(string), typeof(object), typeof(IDbTransaction) }));
                    }
                    else
                    {
                        generator.Emit(OpCodes.Callvirt, typeof(ITycheMapper)
                            .GetMethod("Insert", new Type[] { typeof(string), typeof(object), typeof(IDbTransaction) }));
                    }
                }
                else if (method.GetCustomAttributes(typeof(TycheUpdateAttribute)).Any())
                {
                    generator.Emit(OpCodes.Callvirt, typeof(ITycheMapper)
                        .GetMethod("Update", new Type[] { typeof(string), typeof(object), typeof(IDbTransaction) }));
                }

                generator.Emit(OpCodes.Ret);

                generator.MarkLabel(normal);

                generator.Emit(OpCodes.Ldtoken, mapperResource.Implement);
                generator.Emit(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle", BindingFlags.Public | BindingFlags.Static));
                generator.Emit(OpCodes.Ldc_I4_0);
                generator.Emit(OpCodes.Newarr, typeof(object));
                generator.Emit(OpCodes.Call, typeof(Activator).GetMethod("CreateInstance", new Type[] { typeof(Type), typeof(object[]) }));
                generator.Emit(OpCodes.Castclass, typeof(ITycheMapper));
                generator.Emit(OpCodes.Stloc_S, mapper);

                generator.BeginExceptionBlock();

                generator.Emit(OpCodes.Ldloc_S, mapper);
                generator.Emit(OpCodes.Ldloc_S, sql);
                generator.Emit(OpCodes.Ldloc_S, parameter);

                if (method.GetCustomAttributes(typeof(TycheSelectAttribute)).Any())
                {
                    if (((TycheSelectAttribute)method.GetCustomAttributes(typeof(TycheSelectAttribute)).First()).Multiple)
                    {
                        generator.Emit(OpCodes.Callvirt, typeof(ITycheMapper)
                            .GetMethod("Select", new Type[] { typeof(string), typeof(object) })
                            .MakeGenericMethod(method.ReturnType.GenericTypeArguments[0]));
                    }
                    else
                    {
                        generator.Emit(OpCodes.Callvirt, typeof(ITycheMapper)
                            .GetMethod("SelectSingle", new Type[] { typeof(string), typeof(object) })
                            .MakeGenericMethod(method.ReturnType));
                    }
                }
                else if (method.GetCustomAttributes(typeof(TycheInsertAttribute)).Any())
                {
                    generator.Emit(OpCodes.Ldnull);

                    if (((TycheInsertAttribute)method.GetCustomAttributes(typeof(TycheInsertAttribute)).First()).ReturnId)
                    {
                        generator.Emit(OpCodes.Callvirt, typeof(ITycheMapper)
                            .GetMethod("InsertAndReturnId", new Type[] { typeof(string), typeof(object), typeof(IDbTransaction) }));
                    }
                    else
                    {
                        generator.Emit(OpCodes.Callvirt, typeof(ITycheMapper)
                            .GetMethod("Insert", new Type[] { typeof(string), typeof(object), typeof(IDbTransaction) }));
                    }
                }
                else if (method.GetCustomAttributes(typeof(TycheUpdateAttribute)).Any())
                {
                    generator.Emit(OpCodes.Ldnull);

                    generator.Emit(OpCodes.Callvirt, typeof(ITycheMapper)
                        .GetMethod("Update", new Type[] { typeof(string), typeof(object), typeof(IDbTransaction) }));
                }

                generator.Emit(OpCodes.Stloc_S, result);

                generator.BeginFinallyBlock();

                generator.Emit(OpCodes.Ldloc_S, mapper);
                generator.Emit(OpCodes.Callvirt, mapperResource.Implement.GetMethod("Dispose", Type.EmptyTypes));

                generator.EndExceptionBlock();

                generator.Emit(OpCodes.Ldloc_S, result);
                generator.Emit(OpCodes.Ret);
            }

            var proxy = typeBuilder.CreateType();
            resource.Implement = proxy;
            resource.Proxy = proxy;
        }

        /// <summary>
        /// 注册DAO
        /// </summary>
        /// <typeparam name="T"></typeparam>
        public void Register<T>()
        {
            TycheResourceManager.SetResource(null, typeof(T), null, null, this);
        }
    }
}
